﻿using Swifter.Data.Sql;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Swifter.Data.Linq
{
    partial class Visiter
    {
        public static readonly Visiter Default = new Visiter();

        static Visiter()
        {
            Default.AddVisiter(ExpressionType.Constant, new ConstantVisiable());
            Default.AddVisiter(ExpressionType.MemberAccess, new MemberAccessVisiable());

            Default.AddVisiter(ExpressionType.AndAlso, new BinaryConditionVisiable<Condition, Condition>(Comparisons.And));
            Default.AddVisiter(ExpressionType.OrElse, new BinaryConditionVisiable<Condition, Condition>(Comparisons.Or));

            Default.AddVisiter(ExpressionType.Not, new UnaryConditionVisiable<Condition>(Comparisons.NotEqual, SqlHelper.ValueOf(true)));

            Default.AddVisiter(ExpressionType.Equal, new BinaryConditionVisiable<IValue, IValue>(Comparisons.Equal));
            Default.AddVisiter(ExpressionType.NotEqual, new BinaryConditionVisiable<IValue, IValue>(Comparisons.NotEqual));
            Default.AddVisiter(ExpressionType.GreaterThan, new BinaryConditionVisiable<IValue, IValue>(Comparisons.GreaterThan));
            Default.AddVisiter(ExpressionType.LessThan, new BinaryConditionVisiable<IValue, IValue>(Comparisons.LessThan));
            Default.AddVisiter(ExpressionType.GreaterThanOrEqual, new BinaryConditionVisiable<IValue, IValue>(Comparisons.GreaterEqual));
            Default.AddVisiter(ExpressionType.LessThanOrEqual, new BinaryConditionVisiable<IValue, IValue>(Comparisons.LessEqual));


            Default.AddVisiter(ExpressionType.Add, new BinaryFunctionVisiable<IValue, IValue, AddFunction>((left, right) => new AddFunction(left, right)));
            Default.AddVisiter(ExpressionType.Subtract, new BinaryFunctionVisiable<IValue, IValue, SubtractFunction>((left, right) => new SubtractFunction(left, right)));
            Default.AddVisiter(ExpressionType.Multiply, new BinaryFunctionVisiable<IValue, IValue, MultiplyFunction>((left, right) => new MultiplyFunction(left, right)));
            Default.AddVisiter(ExpressionType.Divide, new BinaryFunctionVisiable<IValue, IValue, DivideFunction>((left, right) => new DivideFunction(left, right)));

            Default.AddVisiter(ExpressionType.MemberAccess, new ColumnAccessVisiable()); 
            Default.AddVisiter(ExpressionType.MemberAccess, new MemberAccessAsColumnVisiable()); 

            Default.AddVisiter(MethodOf<string>(nameof(string.ToUpper)), new Func1Visiable<IValue, UpperFunction>(operand => new UpperFunction(operand)));
            Default.AddVisiter(MethodOf<string>(nameof(string.ToLower)), new Func1Visiable<IValue, LowerFunction>(operand => new LowerFunction(operand)));
            Default.AddVisiter(MethodOf<string>(nameof(string.StartsWith), typeof(string)), new Func2Visiable<IValue, IValue, Condition>((left, right) => Condition(Comparisons.StartWith, left, right)));
            Default.AddVisiter(MethodOf<string>(nameof(string.EndsWith), typeof(string)), new Func2Visiable<IValue, IValue, Condition>((left, right) => Condition(Comparisons.EndWith, left, right)));
            Default.AddVisiter(MethodOf<string>(nameof(string.Contains), typeof(string)), new Func2Visiable<IValue, IValue, Condition>((left, right) => Condition(Comparisons.Contains, left, right)));

            Default.AddVisiter(ExpressionType.New, new NewVisiable());
            Default.AddVisiter(ExpressionType.MemberInit, new MemberInitVisiable());
            Default.AddVisiter(ExpressionType.Parameter, new ParameterVisiable());
            Default.AddVisiter(ExpressionType.Parameter, new ParameterAsColumnVisiable());
        }

        private static MethodInfo MethodOf<T>(string methodName, params Type[] types) =>
            typeof(T).GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static, Type.DefaultBinder, types, null);

        private sealed class ParameterVisiable : IVisitable<ParameterExpression, IEnumerable<ISelectColumn>>
        {
            public IEnumerable<ISelectColumn> Process(Visiter visiter, ScopeInfo scope, ParameterExpression expression)
            {
                var table = scope.GetTable(expression);

                if (table != null)
                {
                    yield return new SelectColumn(new Column(table, "*"));
                }

                // TODO: Non Table
            }
        }

        private sealed class ParameterAsColumnVisiable : IVisitable<ParameterExpression, Column>
        {
            public Column Process(Visiter visiter, ScopeInfo scope, ParameterExpression expression)
            {
                var table = scope.GetTable(expression);

                if (table != null)
                {
                    return table.GetFirstSelectColumnName();
                }

                // TODO: Non Table

                return null;
            }
        }

        private sealed class MemberInitVisiable : IVisitable<MemberInitExpression, IEnumerable<ISelectColumn>>
        {
            public IEnumerable<ISelectColumn> Process(Visiter visiter, ScopeInfo scope, MemberInitExpression expression)
            {
                foreach (var item in visiter.Process<IEnumerable<ISelectColumn>>(scope, expression.NewExpression))
                {
                    yield return item;
                }

                foreach (var item in expression.Bindings)
                {
                    switch (item.BindingType)
                    {
                        case MemberBindingType.Assignment:
                            {
                                var assignment = (MemberAssignment)item;

                                var name = assignment.Member.Name;
                                var value = visiter.Process<IValue>(scope, assignment.Expression);

                                yield return new SelectColumn(value, name);
                            }
                            break;
                        case MemberBindingType.ListBinding:
                            break;
                        case MemberBindingType.MemberBinding:
                            break;
                    }
                }
            }
        }

        private sealed class NewVisiable : IVisitable<NewExpression, IEnumerable<ISelectColumn>>
        {
            public IEnumerable<ISelectColumn> Process(Visiter visiter, ScopeInfo scope, NewExpression expression)
            {
                var length = expression.Arguments.Count;
                var useMember = expression.Members != null && expression.Members.Count == length;

                ParameterInfo[] parameters = null;

                if (!useMember)
                {
                    parameters = expression.Constructor.GetParameters();
                }
                
                for (int i = 0; i < length; i++)
                {
                    var value = visiter.Process<IValue>(scope, expression.Arguments[i]);
                    var member = useMember ? expression.Members[i] : expression.Type.GetMember(parameters[i].Name, BindingFlags.IgnoreCase).FirstOrDefault();

                    member = (member as FieldInfo) ?? (MemberInfo)(member as PropertyInfo);

                    if (member == null)
                    {
                        throw new NotSupportedException("无法匹配到成员信息。" + parameters?[i].Name);
                    }

                    yield return new SelectColumn(value, member.Name);
                }
            }
        }

        private sealed class ConstantVisiable : IVisitable<ConstantExpression, IValue>
        {
            public IValue Process(Visiter visiter, ScopeInfo scope, ConstantExpression expression)
            {
                return SqlHelper.ValueOf(expression.Value);
            }
        }

        private sealed class MemberAccessVisiable : IVisitable<MemberExpression, IValue>
        {
            public IValue Process(Visiter visiter, ScopeInfo scope, MemberExpression expression)
            {
                var method = GetType().GetMethod(nameof(ProcessValue)).MakeGenericMethod(expression.Type);

                return (IValue)method.Invoke(this, new object[] { visiter, scope, expression });
            }

            public IValue ProcessValue<T>(Visiter visiter, ScopeInfo scope, MemberExpression expression)
            {
                var getter = Expression.Lambda<Func<T>>(expression).Compile();

                return SqlHelper.LateValue(getter);
            }
        }

        private sealed class ColumnAccessVisiable : IVisitable<MemberExpression, Column>
        {
            public Column Process(Visiter visiter, ScopeInfo scope, MemberExpression expression)
            {
                var table = scope.GetTable(expression.Expression);

                if (table != null)
                {
                    return new Column(table, expression.Member.Name);
                }

                return null;
            }
        }

        private sealed class MemberAccessAsColumnVisiable : IVisitable<MemberExpression, IEnumerable<ISelectColumn>>
        {
            IEnumerable<ISelectColumn> IVisitable<MemberExpression, IEnumerable<ISelectColumn>>.Process(Visiter visiter, ScopeInfo scope, MemberExpression expression)
            {
                var table = scope.GetTable(expression.Expression);

                if (table != null)
                {
                    yield return new SelectColumn(new Column(table, expression.Member.Name));
                }
            }
        }

        private sealed class Func1Visiable<TOperand, TResult> : IVisitable<MethodCallExpression, TResult> where TOperand : class, IValue
        {
            public Func<TOperand, TResult> Creater;

            public Func1Visiable(Func<TOperand, TResult> creater)
            {
                Creater = creater;
            }

            public TResult Process(Visiter visiter, ScopeInfo scope, MethodCallExpression expression)
            {
                var obj = expression.Method.IsStatic ? expression.Arguments[0] : expression.Object;

                return Creater(visiter.Process<TOperand>(scope, obj));
            }
        }

        private sealed class Func2Visiable<TLeft, TRight, TResult>: IVisitable<MethodCallExpression, TResult> where TLeft : class, IValue where TRight : class, IValue
        {
            public Func<TLeft, TRight, TResult> Creater;

            public Func2Visiable(Func<TLeft, TRight, TResult> creater)
            {
                Creater = creater;
            }

            public TResult Process(Visiter visiter, ScopeInfo scope, MethodCallExpression expression)
            {
                var left = expression.Method.IsStatic ? expression.Arguments[0] : expression.Object;
                var right = expression.Method.IsStatic ? expression.Arguments[1] : expression.Arguments[0];

                return Creater(visiter.Process<TLeft>(scope, left), visiter.Process<TRight>(scope, right));
            }
        }

        private sealed class UnaryFunctionVisiable<TOperand, TResult> : IVisitable<UnaryExpression, TResult> where TOperand : class, IValue where TResult : UnaryFunction
        {
            public Func<TOperand, TResult> Creater;

            public UnaryFunctionVisiable(Func<TOperand, TResult> creater)
            {
                Creater = creater;
            }

            public TResult Process(Visiter visiter, ScopeInfo scope, UnaryExpression expression)
            {
                return Creater(visiter.Process<TOperand>(scope, expression.Operand));
            }
        }

        private sealed class UnaryConditionVisiable<TOperand> : IVisitable<UnaryExpression, Condition> where TOperand : class, IValue
        {
            public Comparisons Comparison { get; }

            public IValue Right { get; }

            public UnaryConditionVisiable(Comparisons comparison, IValue right)
            {
                Comparison = comparison;

                Right = right;
            }

            public Condition Process(Visiter visiter, ScopeInfo scope, UnaryExpression expression)
            {
                return Condition(Comparison, visiter.Process<TOperand>(scope, expression.Operand), Right);
            }
        }

        private sealed class BinaryConditionVisiable<TLeft, TRight> : IVisitable<BinaryExpression, Condition> where TLeft : class, IValue where TRight : class, IValue
        {
            public Comparisons Comparison { get; }

            public BinaryConditionVisiable(Comparisons comparison)
            {
                Comparison = comparison;
            }

            public Condition Process(Visiter visiter, ScopeInfo scope, BinaryExpression expression)
            {
                return Condition(Comparison, visiter.Process<TLeft>(scope, expression.Left), visiter.Process<TRight>(scope, expression.Right));
            }
        }

        private sealed class BinaryFunctionVisiable<TLeft, TRight, TResult> : IVisitable<BinaryExpression, TResult> where TLeft : class, IValue where TRight : class, IValue where TResult : BinaryFunction
        {
            public Func<TLeft, TRight, TResult> Creater;

            public BinaryFunctionVisiable(Func<TLeft, TRight, TResult> creater)
            {
                Creater = creater;
            }

            public TResult Process(Visiter visiter, ScopeInfo scope, BinaryExpression expression)
            {
                return Creater(visiter.Process<TLeft>(scope, expression.Left), visiter.Process<TRight>(scope, expression.Right));
            }
        }
    }
}